#include "drv_spi.h"

#include "bsp_pinctrl.h"

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#define SPI_TX_EVT_SRC                  (EVT_SRC_SPI1_SPTI)

/* DMA definition */
#define DMA_UNIT                        (CM_DMA2)
#define DMA_CLK                         (FCG0_PERIPH_DMA2 | FCG0_PERIPH_AOS)
#define DMA_TX_CH                       (DMA_CH0)
#define DMA_TX_TRIG_CH                  (AOS_DMA2_0)

#define DMA_TX_INT_CH                   (DMA_INT_TC_CH0)
#define DMA_TX_INT_SRC                  (INT_SRC_DMA2_TC0)
#define DMA_TX_IRQ_NUM                  (INT008_IRQn)

#define DMA_CH_REG(reg_base, ch)        (*(__IO uint32_t *)((uint32_t)(&(reg_base)) + ((ch) * 0x40UL)))
#define DMA_SW_TRIGGER_UNLOCK           (0xA1UL << DMA_SWREQ_SWREQWP_POS)

static SemaphoreHandle_t xSPI1Semaphore;

static void DMA_TransCompleteCallback(void)
{
    BaseType_t xTaskWokenByReceive = pdFALSE;
    xSemaphoreGiveFromISR(xSPI1Semaphore, &xTaskWokenByReceive);
    
    DMA_ClearTransCompleteStatus(DMA_UNIT, DMA_TX_INT_CH);
    
    if( xTaskWokenByReceive != pdFALSE )
    {
        /* We should switch context so the ISR returns to a different task.
           NOTE: How this is done depends on the port you are using. Check
           the documentation and examples for your port. */
        taskYIELD ();
    }
}

void SPI1_Init(void)
{
    stc_spi_init_t stcSpiInit;
    stc_irq_signin_config_t stcIrqSignConfig;
    stc_dma_init_t stcDmaInit;
    
    Pinctrl_SPI1_Init();
    
    /* Configuration SPI */
    FCG_Fcg1PeriphClockCmd(SPI_CLK, ENABLE);
    SPI_StructInit(&stcSpiInit);
    stcSpiInit.u32WireMode          = SPI_4_WIRE;
    stcSpiInit.u32TransMode         = SPI_SEND_ONLY;
    stcSpiInit.u32MasterSlave       = SPI_MASTER;
    stcSpiInit.u32Parity            = SPI_PARITY_INVD;
    stcSpiInit.u32SpiMode           = SPI_MD_3;
    stcSpiInit.u32BaudRatePrescaler = SPI_BR_CLK_DIV2;
    stcSpiInit.u32DataBits          = SPI_DATA_SIZE_8BIT;
    stcSpiInit.u32FirstBit          = SPI_FIRST_MSB;
    stcSpiInit.u32FrameLevel        = SPI_1_FRAME;
    (void)SPI_Init(SPI_UNIT, &stcSpiInit);
    
    SPI_Cmd(SPI_UNIT, ENABLE);
    
    if(xSPI1Semaphore == NULL)
    {
        xSPI1Semaphore = xSemaphoreCreateBinary();
    }
    
    /* DMA configuration */
    FCG_Fcg0PeriphClockCmd(DMA_CLK, ENABLE);
    (void)DMA_StructInit(&stcDmaInit);
    stcDmaInit.u32BlockSize  = 1UL;
    stcDmaInit.u32TransCount = 0;
    stcDmaInit.u32DataWidth  = DMA_DATAWIDTH_16BIT;
    /* Configure TX */
    stcDmaInit.u32IntEn       = DMA_INT_ENABLE;
    stcDmaInit.u32SrcAddrInc  = DMA_SRC_ADDR_FIX;
    stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_FIX;
    stcDmaInit.u32SrcAddr     = (uint32_t)0;
    stcDmaInit.u32DestAddr    = (uint32_t)(&SPI_UNIT->DR);
    if (LL_OK != DMA_Init(DMA_UNIT, DMA_TX_CH, &stcDmaInit)) {
        for (;;) {
        }
    }
    AOS_SetTriggerEventSrc(DMA_TX_TRIG_CH, SPI_TX_EVT_SRC);
    
    /* DMA receive NVIC configure */
    stcIrqSignConfig.enIntSrc    = DMA_TX_INT_SRC;
    stcIrqSignConfig.enIRQn      = DMA_TX_IRQ_NUM;
    stcIrqSignConfig.pfnCallback = &DMA_TransCompleteCallback;
    (void)INTC_IrqSignIn(&stcIrqSignConfig);
    NVIC_ClearPendingIRQ(stcIrqSignConfig.enIRQn);
    NVIC_SetPriority(stcIrqSignConfig.enIRQn, DDL_IRQ_PRIO_03);
    NVIC_EnableIRQ(stcIrqSignConfig.enIRQn);
    
    DMA_Cmd(DMA_UNIT, ENABLE);
}

void SPI1_DMA_Trans(uint8_t *pvTxBuf, uint32_t u32TxLen, uint32_t u32DataWidth, uint32_t u32SrcAddrInc)
{
    stc_dma_init_t stcDmaInit;
    
    WRITE_REG32(DMA_CH_REG(DMA_UNIT->SAR0, DMA_TX_CH), (uint32_t)pvTxBuf);
    MODIFY_REG32(DMA_CH_REG(DMA_UNIT->DTCTL0, DMA_TX_CH), DMA_DTCTL_CNT, ((uint32_t)(u32TxLen) << DMA_DTCTL_CNT_POS));
    MODIFY_REG32(DMA_CH_REG(DMA_UNIT->CHCTL0, DMA_TX_CH), DMA_CHCTL_HSIZE, u32DataWidth);
    MODIFY_REG32(DMA_CH_REG(DMA_UNIT->CHCTL0, DMA_TX_CH), DMA_CHCTL_SINC, u32SrcAddrInc);
    
    /* Enable DMA channel */
    WRITE_REG32(DMA_UNIT->CHEN, ((1UL << DMA_TX_CH) & DMA_CHEN_CHEN));
    /* Start SW trigger */
    WRITE_REG32(DMA_UNIT->SWREQ, (DMA_SW_TRIGGER_UNLOCK | DMA_MX_CH0));
    
    xSemaphoreTake(xSPI1Semaphore, portMAX_DELAY);
}
